package com.mrxu.monitor.bean;

import com.mrxu.monitor.config.AttackConfig;
import com.mrxu.monitor.config.BusinessCodeConfig;
import com.mrxu.monitor.config.MonitorProperties;
import com.mrxu.monitor.config.StatusConfig;
import com.mrxu.monitor.constant.RedisConstant;
import com.mrxu.monitor.notice.MessageCenterService;
import com.mrxu.monitor.notice.NoticeType;
import com.mrxu.monitor.service.IpService;
import com.mrxu.monitor.util.CommonUtil;
import com.mrxu.monitor.util.DateUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

@Slf4j
@Data
public class DayCount {


    /**
     * 上一次
     */
    private static String lastDayStr;

    private static Map<String,String> attackIpPool = new ConcurrentHashMap<>();

    private IpService ipService;

    private MonitorProperties monitorProperties;

    private StringRedisTemplate redisTemplate;

    private MessageCenterService messageCenterService;

    /**
     * 初始化属性
     * @param ipService
     * @param redisTemplate
     * @param monitorProperties
     */
    public void initProperty(StringRedisTemplate redisTemplate, IpService ipService, MessageCenterService messageCenterService, MonitorProperties monitorProperties){
        this.ipService = ipService;
        this.monitorProperties = monitorProperties;
        this.redisTemplate = redisTemplate;
        this.messageCenterService = messageCenterService;
    }

    public void startRequest(RequestInfo requestInfo) {
        //1、pv
        pv.getAndIncrement();

        // 2、uv判断待加
        dillUvCount(requestInfo.getIp());

        //3、请求端类型
        if(requestInfo.isMobile()) {
            mobileCount.getAndIncrement();
        } else {
            pcCount.getAndIncrement();
        }

        //4、浏览器类型
        String browser = requestInfo.getBrowser();
        AtomicLong browserCount = browserMap.get(browser);
        if(browserCount == null){
            browserCount = initMapValue(browserMap,browser);
        }
        browserCount.getAndIncrement();

    }

    public void endRequest(RequestInfo requestInfo) {
        //1、接口流量
        flowSum.addAndGet(requestInfo.getResponseSize());
        if (404 != requestInfo.getStatus() && 304 != requestInfo.getStatus()) {
            //2、请求路径次数
            String path = requestInfo.getPath();
            AtomicLong pathCount = pathCountMap.get(path);
            if(pathCount == null){
                pathCount = initMapValue(pathCountMap,path);
            }
            pathCount.getAndIncrement();

            //3、接口总耗时
            AtomicLong pathTime = pathTimeMap.get(path);
            if(pathTime == null) {
                pathTime = initMapValue(pathTimeMap,path);
            }
            long spendTime = requestInfo.getEndTime()-requestInfo.getStartTime();
            pathTime.addAndGet(spendTime);

            //统计超时时间的接口
            if(monitorProperties.getTimeOutConfig().isEnable()
                    && spendTime >= monitorProperties.getTimeOutConfig().getLimitMilliSecond()){
                //超过配置时间才认定为次数
                timeoutCount.incrementAndGet();
            }
        }

        //3、httpstatus
        Integer status = requestInfo.getStatus();
        AtomicLong statusCount = httpStatusMap.get(status);
        if(statusCount == null) {
            statusCount = initMapValue(httpStatusMap,status+"");
        }
        statusCount.incrementAndGet();

        //4、请求次数
        if(status != 200 && status != 304){
            //4、异常数量
            exceptionCount.incrementAndGet();
        }
    }

    /**
     * 将缓存数据写入redis
     */
    public void flush2Redis() {
        if(pv.get() <= 0) {
            //没有请求缓存的值，直接return
            attackIpPool.clear();
            return;
        }
        //设置redis过期时间
        String currentDay = DateUtil.getTodayStr();
        if(!StringUtils.hasLength(lastDayStr) || !currentDay.equalsIgnoreCase(lastDayStr)){
            initRedisKeyExpire(lastDayStr);
            lastDayStr = currentDay;
        }

        //1、statstic数据
        doFlushStatics(currentDay);
        //2.浏览器类型
        if(browserMap.size() > 0) {
            String redisBrowserKey = RedisConstant.getKey(RedisConstant.BROWSER,currentDay);
            Iterator<String> browserIt = browserMap.keySet().iterator();
            while(browserIt.hasNext()){
                String browser = browserIt.next();
                redisTemplate.opsForZSet().incrementScore(redisBrowserKey,browser,browserMap.get(browser).get());
            }
            browserMap.clear();
        }
        //3、4pathinfo
        doFlushPathInfo(currentDay);
        //5、httstatus
        doFlushStatus(currentDay);
        //6、国家省市统计
        doFlushProvinceCityAttackInfo(currentDay);
    }

    /**
     * redis过期时间
     * @param currentStr
     */
    private void initRedisKeyExpire(String currentStr){
        String redisKeyStatstic = RedisConstant.getKey(RedisConstant.STASTIC,currentStr);
        String redisKeyPathTime = RedisConstant.getKey(RedisConstant.PATHTIME,currentStr);
        String redisKeyPathCount = RedisConstant.getKey(RedisConstant.PATHCOUNT,currentStr);
        String redisKeyCountry = RedisConstant.getKey(RedisConstant.COUNTRY,currentStr);
        String redisKeyProvince = RedisConstant.getKey(RedisConstant.PROVINCE,currentStr);
        String redisKeyCity = RedisConstant.getKey(RedisConstant.CITY,currentStr);
        String redisKeyBrowser = RedisConstant.getKey(RedisConstant.BROWSER,currentStr);
        String redisKeyHttpStatus = RedisConstant.getKey(RedisConstant.HTTPSTATUS,currentStr);
        String redisKeyException = RedisConstant.getKey(RedisConstant.EXCEPTION,currentStr);
        Long ttlStatstic = redisTemplate.getExpire(redisKeyStatstic);
        if(ttlStatstic==-1){
            //当前key存在没设置过期时间
            redisTemplate.expire(redisKeyStatstic,30, TimeUnit.DAYS);
        }
        Long ttlPathTime = redisTemplate.getExpire(redisKeyPathTime);
        if(ttlPathTime==-1){
            //当前key存在没设置过期时间
            redisTemplate.expire(redisKeyPathTime,DateUtil.getLeftHousrOfToday(), TimeUnit.HOURS);
        }
        Long ttlPathCount = redisTemplate.getExpire(redisKeyPathCount);
        if(ttlPathCount==-1){
            //当前key存在没设置过期时间
            redisTemplate.expire(redisKeyPathCount,DateUtil.getLeftHousrOfToday(), TimeUnit.HOURS);
        }
        Long ttlPathCountry = redisTemplate.getExpire(redisKeyCountry);
        if(ttlPathCountry==-1){
            //当前key存在没设置过期时间
            redisTemplate.expire(redisKeyCountry,DateUtil.getLeftHousrOfToday(), TimeUnit.HOURS);
        }
        Long ttlProvince = redisTemplate.getExpire(redisKeyProvince);
        if(ttlProvince==-1){
            //当前key存在没设置过期时间
            redisTemplate.expire(redisKeyProvince,DateUtil.getLeftHousrOfToday(), TimeUnit.HOURS);
        }
        Long ttlCity = redisTemplate.getExpire(redisKeyCity);
        if(ttlCity==-1){
            //当前key存在没设置过期时间
            redisTemplate.expire(redisKeyCity,DateUtil.getLeftHousrOfToday(), TimeUnit.HOURS);
        }
        Long ttlBroser = redisTemplate.getExpire(redisKeyBrowser);
        if(ttlBroser==-1){
            //当前key存在没设置过期时间
            redisTemplate.expire(redisKeyBrowser,DateUtil.getLeftHousrOfToday(), TimeUnit.HOURS);
        }
        Long ttlHttpStatus = redisTemplate.getExpire(redisKeyHttpStatus);
        if(ttlHttpStatus==-1){
            //当前key存在没设置过期时间
            redisTemplate.expire(redisKeyHttpStatus,DateUtil.getLeftHousrOfToday(), TimeUnit.HOURS);
        }
        Long ttlException = redisTemplate.getExpire(redisKeyException);
        if(ttlException==-1){
            //当前key存在没设置过期时间
            redisTemplate.expire(redisKeyException,DateUtil.getLeftHousrOfToday(), TimeUnit.HOURS);
        }
    }

    /**
     * 写入静态信息
     * @param currentDay 日期
     */
    private void doFlushStatics(String currentDay) {
        String redisStaticsKey = RedisConstant.getKey(RedisConstant.STASTIC,currentDay);
        redisTemplate.opsForHash().increment(redisStaticsKey,"pv",pv.get());
        pv.set(0);

        if(uv.get() > 0 ) {
            redisTemplate.opsForHash().increment(redisStaticsKey,"uv",uv.get());
            uv.set(0);
        }

        if(mobileCount.get() > 0) {
            redisTemplate.opsForHash().increment(redisStaticsKey,"mobile",mobileCount.get());
            mobileCount.set(0);
        }

        if(pcCount.get() > 0) {
            redisTemplate.opsForHash().increment(redisStaticsKey,"pc",pcCount.get());
            pcCount.set(0);
        }
        if(flowSum.get() > 0) {
            redisTemplate.opsForHash().increment(redisStaticsKey,"flow",flowSum.get());
            flowSum.set(0);
        }
        if(monitorProperties.getTimeOutConfig().isEnable()) {
            int limitCount = monitorProperties.getTimeOutConfig().getLimitCountPerSecond();
            if(timeoutCount.get() >= limitCount) {
                //触发告警
                messageCenterService.sendNoticeMsg("1秒钟内响应超过配置时间["
                        +monitorProperties.getTimeOutConfig().getLimitMilliSecond()
                        +"]毫秒的接口超过次数阈值【"+limitCount+"】当前数量:"+timeoutCount.get()+",请注意排查", NoticeType.TimeoutCount);
                String redisAttackIpKey = RedisConstant.getKey(RedisConstant.EXCEPTION,currentDay);
                String currentStr = attackIpPool.get("httpTimeoutError");
                if(!StringUtils.hasLength(currentStr)){
                    //不存在上次超限
                    currentStr = DateUtil.getCurrentStr();
                    attackIpPool.put("httpTimeoutError",currentStr);
                    redisTemplate.opsForHash().increment(redisStaticsKey,"alarm",1);
                }
                double scoreInfo = timeoutCount.get() + 0.0000000001;
                //存在上次攻击，找到上次时间的ip持续时间++
                redisTemplate.opsForZSet().incrementScore(redisAttackIpKey,currentStr+"_httpTimeoutError",scoreInfo);
            } else {
                //写入的时候此已经没有触发告警
                attackIpPool.remove("httpTimeoutError");
            }
            timeoutCount.set(0);
        }

        if(exceptionCount.get() > 0) {
            StatusConfig statusConfig = monitorProperties.getHttpStatusConfig();
            int limitCount = statusConfig.getHttpStatusErrorCountLimitPerSecond();
            //判断是否触发告警
            if(statusConfig.isEnable() && exceptionCount.get() >= limitCount) {
                //触发告警
                messageCenterService.sendNoticeMsg("1秒钟内httpstatus错误码次数超过阈值【"+limitCount+"】,请注意排查", NoticeType.ErrorCount);
                String redisAttackIpKey = RedisConstant.getKey(RedisConstant.EXCEPTION,currentDay);
                String currentStr = attackIpPool.get("httpStatusError");
                if(!StringUtils.hasLength(currentStr)) {
                    //不存在上次超限
                    currentStr = DateUtil.getCurrentStr();
                    attackIpPool.put("httpStatusError",currentStr);
                    redisTemplate.opsForHash().increment(redisStaticsKey,"alarm",1);
                }
                double scoreInfo = exceptionCount.get() + 0.0000000001;
                //存在上次攻击，找到上次时间的ip持续时间++
                redisTemplate.opsForZSet().incrementScore(redisAttackIpKey,currentStr+"_httpStatusError",scoreInfo);
            } else {
                //写入的时候此已经没有触发告警
                attackIpPool.remove("httpStatusError");
            }
            redisTemplate.opsForHash().increment(redisStaticsKey,"exception",exceptionCount.get());
            exceptionCount.set(0);
        }

        if(businessErrorCount.get() > 0) {
            BusinessCodeConfig bizCodeConfig = monitorProperties.getBusinessCode();
            int limitCount = bizCodeConfig.getLimitErrorCountPerSecond();
            //判断是否触发告警
            if(bizCodeConfig.isEnable() && businessErrorCount.get() >= limitCount) {
                //触发告警
                messageCenterService.sendNoticeMsg("1秒钟内业务类型错误码次数超过阈值【"+limitCount+"】,请注意排查", NoticeType.ErrorCount);
                String redisAttackIpKey = RedisConstant.getKey(RedisConstant.EXCEPTION,currentDay);
                String currentStr = attackIpPool.get("bizCodeError");
                if(!StringUtils.hasLength(currentStr)) {
                    //不存在上次超限
                    currentStr = DateUtil.getCurrentStr();
                    attackIpPool.put("bizCodeError",currentStr);
                    redisTemplate.opsForHash().increment(redisStaticsKey,"alarm",1);
                }
                double scoreInfo = businessErrorCount.get() + 0.0000000001;
                //存在上次攻击，找到上次时间的ip持续时间++
                redisTemplate.opsForZSet().incrementScore(redisAttackIpKey,currentStr+"_bizCodeError",scoreInfo);
            } else {
                //写入的时候此已经没有触发告警
                attackIpPool.remove("bizCodeError");
            }
            redisTemplate.opsForHash().increment(redisStaticsKey,"bizCodeError",businessErrorCount.get());
            businessErrorCount.set(0);
        }

    }

    /**
     * 状态码相关的
     * @param currentYear
     */
    private void doFlushStatus(String currentYear){
        if(httpStatusMap.size() > 0) {
            String redisHttpSatusKey = RedisConstant.getKey(RedisConstant.HTTPSTATUS,currentYear);
            Iterator<String> statusIt = httpStatusMap.keySet().iterator();
            while(statusIt.hasNext()) {
                String status = statusIt.next();
                redisTemplate.opsForHash().increment(redisHttpSatusKey,status,httpStatusMap.get(status).get());
            }
            httpStatusMap.clear();
        }
    }

    private void doFlushPathInfo(String currentDay) {
        // 3、pathTime todo 分布式锁
        if(pathTimeMap.size() > 0) {
            String redisPathTimeKey = RedisConstant.getKey(RedisConstant.PATHTIME,currentDay);
            Iterator<String> pathTimeIt = pathTimeMap.keySet().iterator();
            while(pathTimeIt.hasNext()) {
                String path = pathTimeIt.next();
                if(pathCountMap.get(path) == null || pathCountMap.get(path).get()<=0) {
                    continue;
                }
                Long avgTime = pathTimeMap.get(path).get();
                Double dbAvgTime = redisTemplate.opsForZSet().score(redisPathTimeKey,path);
                if(dbAvgTime != null && dbAvgTime > 0) {
                    String redisPathCountKey = RedisConstant.getKey(RedisConstant.PATHCOUNT,currentDay);
                    Long dbCount = redisTemplate.opsForZSet().score(redisPathCountKey,path).longValue();
                    dbAvgTime = dbAvgTime * dbCount;
                    avgTime = ((avgTime+dbAvgTime.longValue())/(pathCountMap.get(path).get()+dbCount));
                } else {
                    avgTime = avgTime/pathCountMap.get(path).get();
                }
                redisTemplate.opsForZSet().add(redisPathTimeKey,path,avgTime);
            }
            pathTimeMap.clear();
        }

        //4、pathcount
        if(pathCountMap.size() > 0) {
            String redisPathCountKey = RedisConstant.getKey(RedisConstant.PATHCOUNT,currentDay);
            Iterator<String> pathCountIt = pathCountMap.keySet().iterator();
            while(pathCountIt.hasNext()) {
                String path = pathCountIt.next();
                redisTemplate.opsForZSet().incrementScore(redisPathCountKey,path,pathCountMap.get(path).get());
            }
            pathCountMap.clear();
        }
    }

    /**
     * 写入省市攻击等信息
     * @param currentYear 当前处理的时间str
     */
    private void doFlushProvinceCityAttackInfo(String currentYear){
        if(ipCountMap.size() > 0) {
            String redisStasticKey = RedisConstant.getKey(RedisConstant.STASTIC,currentYear);
            String redisCountryKey = RedisConstant.getKey(RedisConstant.COUNTRY,currentYear);
            String redisProvinceKey = RedisConstant.getKey(RedisConstant.PROVINCE,currentYear);
            String redisCityKey = RedisConstant.getKey(RedisConstant.CITY,currentYear);
            Iterator<String> ipIt = ipCountMap.keySet().iterator();
            while (ipIt.hasNext()) {
                String ip = ipIt.next();
                Long count = ipCountMap.get(ip).get();
                IpRegion region = ipService.getRegionInfo(ip);
                if (region.isChina()) {
                    redisTemplate.opsForZSet().incrementScore(redisCountryKey,region.getCountry(),count);
                    redisTemplate.opsForZSet().incrementScore(redisProvinceKey,region.getProvince(),count);
                    redisTemplate.opsForZSet().incrementScore(redisCityKey,region.getCity(),count);
                } else if(region.isUnkown()) {
                    redisTemplate.opsForZSet().incrementScore(redisProvinceKey,"未知",count);
                    redisTemplate.opsForZSet().incrementScore(redisCityKey,"未知",count);
                } else if(region.isLocalIp()) {
                    redisTemplate.opsForZSet().incrementScore(redisProvinceKey,"本机",count);
                    redisTemplate.opsForZSet().incrementScore(redisCityKey,"本机",count);
                } else {
                    //国外
                    redisTemplate.opsForZSet().incrementScore(redisCountryKey,region.getCountry(),count);
                }
                AttackConfig attackConfig = monitorProperties.getAttack();
                if(attackConfig.isEnable()
                        && !CommonUtil.matchPath(attackConfig.getExcludeIp(),ip)
                        && count >= attackConfig.getLimitCountPerSecond()){
                    messageCenterService.sendNoticeMsg("1秒钟内IP("+ip+")访问次数超过阈值【"+attackConfig.getLimitCountPerSecond()+"】,请注意排查", NoticeType.Attack);
                    //ip没有排除在外的
                    //每秒100次认定为攻击默认
                    String redisAttackIpKey = RedisConstant.getKey(RedisConstant.EXCEPTION,currentYear);
                    String currentStr = attackIpPool.get(ip);
                    if(!StringUtils.hasLength(currentStr)) {
                        //不存在上次攻击
                        //4、攻击ip缓存信息
                        currentStr = DateUtil.getCurrentStr();
                        attackIpPool.put(ip,currentStr);
                        //攻击次数
                        redisTemplate.opsForHash().increment(redisStasticKey,"alarm",1);
                    }
                    double scoreInfo = count + 0.0000000001;
                    //存在上次攻击，找到上次时间的ip持续时间++
                    redisTemplate.opsForZSet().incrementScore(redisAttackIpKey,currentStr+"_"+ip,scoreInfo);
                } else {
                    //写入的时候此ip已经没有触发告警
                    attackIpPool.remove(ip);
                }
            }
            ipCountMap.clear();
        }
    }

    private void clear(){
        pv.set(0);
        uv.set(0);
        mobileCount.set(0);
        pcCount.set(0);
        exceptionCount.set(0);
        flowSum.set(0);
        ipCountMap.clear();
        browserMap.clear();
        pathCountMap.clear();
        pathTimeMap.clear();
        httpStatusMap.clear();
        timeoutCount.set(0);
    }

    /**
     * 处理uv的次数
     * @param ip 请求ip对象
     */
    private void dillUvCount(String ip) {
        AtomicLong ipCount = ipCountMap.get(ip);
        if(ipCount == null) {
            ipCount = initMapValue(ipCountMap,ip);
            //此ip本地缓存没有,查询redis
            String redisIpSetKey = RedisConstant.getKey("ipset", DateUtil.getTodayStr());
            if(redisTemplate.opsForHash().putIfAbsent(redisIpSetKey,ip,"1")) {
                //uv++
                uv.incrementAndGet();
            }
        }
        ipCount.getAndIncrement();
    }

    /**
     * 初始化map信息
     * @param map map
     * @param key value值
     * @return 结果
     */
    private synchronized AtomicLong initMapValue(Map<String,AtomicLong> map,String key) {
        if(map.get(key) == null) {
            map.put(key,new AtomicLong());
        }
        return map.get(key);
    }


    /**
     * 耗时接口统计
     */
    private AtomicLong timeoutCount = new AtomicLong();


    /**
     * 今日pv
     */
    private AtomicLong pv = new AtomicLong();

    /**
     * 今日uv
     */
    private AtomicLong uv = new AtomicLong();

    /**
     * mobile次数
     */
    private AtomicLong mobileCount = new AtomicLong();

    /**
     * mobile次数
     */
    private AtomicLong pcCount = new AtomicLong();

    /**
     * 异常数量
     */
    private AtomicLong exceptionCount = new AtomicLong();

    /**
     * 业务异常数量
     */
    private AtomicLong businessErrorCount = new AtomicLong();

    /**
     * 流量
     */
    private AtomicLong flowSum = new AtomicLong();

    /**
     * ip次数统计
     */
    private Map<String,AtomicLong> ipCountMap = new Hashtable<>();

    /**
     * 浏览器
     */
    private Map<String,AtomicLong> browserMap = new Hashtable<>();

    /**
     * 今日接口请求信息
     */
    private Map<String,AtomicLong> pathCountMap = new Hashtable<>();
    /**
     * 今日接口平均耗时
     * 这里缓存接口耗时合计
     */
    private Map<String,AtomicLong> pathTimeMap = new Hashtable<>();

    /**
     * 今日接口状态码请求信息
     */
    private Map<String,AtomicLong> httpStatusMap = new Hashtable<>();

}
